package com.ruoyi.recycle.service.impl;

import cn.hutool.core.lang.Assert;
import cn.hutool.core.util.CharsetUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.http.HttpUtil;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.github.wxpay.sdk.WXPayUtil;
import com.ruoyi.recycle.constant.OrderInfoConstant;
import com.ruoyi.recycle.domain.BillRecord;
import com.ruoyi.recycle.domain.ExchangeRecord;
import com.ruoyi.recycle.dto.*;
import com.ruoyi.recycle.exception.BusinessException;
import com.ruoyi.recycle.factory.OrderFactory;
import com.ruoyi.recycle.properties.Wxproperties;
import com.ruoyi.recycle.service.BillRecordService;
import com.ruoyi.recycle.service.ExchangeRecordService;
import com.ruoyi.system.api.util.AesUtil;
import com.wechat.pay.contrib.apache.httpclient.WechatPayHttpClientBuilder;
import com.wechat.pay.contrib.apache.httpclient.auth.AutoUpdateCertificatesVerifier;
import com.wechat.pay.contrib.apache.httpclient.auth.PrivateKeySigner;
import com.wechat.pay.contrib.apache.httpclient.auth.WechatPay2Credentials;
import com.wechat.pay.contrib.apache.httpclient.auth.WechatPay2Validator;
import com.wechat.pay.contrib.apache.httpclient.util.PemUtil;
import lombok.RequiredArgsConstructor;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.util.EntityUtils;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.Base64Utils;

import javax.annotation.PostConstruct;
import javax.servlet.http.HttpServletRequest;
import java.io.*;
import java.math.BigDecimal;
import java.nio.charset.StandardCharsets;
import java.security.PrivateKey;
import java.security.Signature;
import java.security.cert.CertificateException;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
import java.util.*;
import java.util.stream.Collectors;
import java.util.stream.Stream;

@Component
@Slf4j
@RequiredArgsConstructor
public class WechatPaymentService {
    private final Wxproperties wxproperties;
    private PrivateKey merchantPrivateKey;
    private static String nonceString;
    private static CloseableHttpClient httpClient;
    private final OrderFactory orderFactory;
    private final BillRecordService billService;

    @PostConstruct
    private void initHttpClient() {
        try {
            // 加载商户私钥（privateKey：私钥字符串）
            String request = HttpUtil.get("https://bluestarhuman.com/wx/apiclient_key.pem");
            final InputStream inputStream = new ByteArrayInputStream(request.getBytes(CharsetUtil.UTF_8));
            merchantPrivateKey = PemUtil.loadPrivateKey(inputStream);
            // 加载平台证书（mchId：商户号,mchSerialNo：商户证书序列号,apiV3Key：V3密钥）
            AutoUpdateCertificatesVerifier verifier = new AutoUpdateCertificatesVerifier(
                    new WechatPay2Credentials(wxproperties.getMchId(), new PrivateKeySigner(wxproperties.getMchSerialNo(), merchantPrivateKey)), wxproperties.getApiV3Key().getBytes("utf-8"));
            nonceString = WXPayUtil.generateNonceStr();
            // 初始化httpClient
            httpClient = WechatPayHttpClientBuilder.create()
                    .withMerchant(wxproperties.getMchId(), wxproperties.getMchSerialNo(), merchantPrivateKey)
                    .withValidator(new WechatPay2Validator(verifier)).build();
            log.info("初始化微信支付请求client成功");
        } catch (Exception e) {
            log.error("初始化微信支付请求client错误,原因:{}", e.getMessage());
        }
    }
    @SneakyThrows
    public Map<String, String> pay(PaymentDTO paymentDTO) {
        final String orderNo = paymentDTO.getOrderNo();
        int amount = paymentDTO.getTotalFee().multiply(BigDecimal.valueOf(100L)).intValue();
        HttpPost httpPost = new HttpPost(wxproperties.getPayV3Url());
        httpPost.addHeader("Accept", "application/json");
        httpPost.addHeader("Content-type", "application/json; charset=utf-8");
        ByteArrayOutputStream bos = new ByteArrayOutputStream();
        ObjectMapper objectMapper = new ObjectMapper();
        ObjectNode rootNode = objectMapper.createObjectNode();
        rootNode.put("mchid", wxproperties.getMchId())
                .put("appid", paymentDTO.getAppId())
                .put("description", "商品")
                .put("notify_url", paymentDTO.getNotifyUrl())
                .put("out_trade_no",orderNo);
        rootNode.putObject("amount")
                .put("total", amount);
        rootNode.putObject("payer")
                .put("openid", paymentDTO.getOpenId());
        objectMapper.writeValue(bos, rootNode);
        httpPost.setEntity(new StringEntity(bos.toString("UTF-8"), "UTF-8"));
        CloseableHttpResponse response = httpClient.execute(httpPost);
        String bodyAsString = EntityUtils.toString(response.getEntity());
        final JSONObject jsonObject = JSONObject.parseObject(bodyAsString);
        log.info("微信支付返回信息 = " + bodyAsString);
        final String prepay_id = jsonObject.getString("prepay_id");
        Assert.isTrue(StrUtil.isNotEmpty(prepay_id), () -> new BusinessException("支付失败"));
        Map<String, String> returnMap = new HashMap<>(7);
        String timestamp = String.valueOf(System.currentTimeMillis() / 1000);
        returnMap.put("timeStamp", timestamp);
        returnMap.put("status", "success");
        returnMap.put("package", "prepay_id=" + prepay_id);
        returnMap.put("appId", paymentDTO.getAppId());
        returnMap.put("nonceStr", nonceString);
        returnMap.put("signType", "RSA");

        String payMessage = paymentDTO.getAppId() + "\n"
                + timestamp + "\n"
                + nonceString + "\n"
                + returnMap.get("package") + "\n";
        returnMap.put("paySign", sign(payMessage.getBytes(StandardCharsets.UTF_8)));
        return returnMap;
    }

    @SneakyThrows
    public String nativePay(NativePayDTO payDTO) {
        int amount = payDTO.getAmount().multiply(BigDecimal.valueOf(100L)).intValue();
        HttpPost httpPost = new HttpPost(wxproperties.getNativePayUrl());
        httpPost.addHeader("Accept", "application/json");
        httpPost.addHeader("Content-type", "application/json; charset=utf-8");
        ByteArrayOutputStream bos = new ByteArrayOutputStream();
        ObjectMapper objectMapper = new ObjectMapper();
        ObjectNode rootNode = objectMapper.createObjectNode();
        rootNode.put("mchid", wxproperties.getMchId())
                .put("appid", payDTO.getAppId())
                .put("description", "商品")
                .put("notify_url", payDTO.getNotifyUrl())
                .put("out_trade_no", payDTO.getOrderNo());
        rootNode.putObject("amount")
                .put("total", amount);
        objectMapper.writeValue(bos, rootNode);
        httpPost.setEntity(new StringEntity(bos.toString("UTF-8"), "UTF-8"));
        CloseableHttpResponse response = httpClient.execute(httpPost);
        String bodyAsString = EntityUtils.toString(response.getEntity());
        final JSONObject jsonObject = JSONObject.parseObject(bodyAsString);
        log.info("微信二维码展示支付返回信息 = " + bodyAsString);
        final String codeUrl = jsonObject.getString("code_url");
        Assert.isTrue(StrUtil.isNotEmpty(codeUrl), () -> new BusinessException("支付失败"));
        return codeUrl;
    }

    @SneakyThrows
    public Map<String, Object> transfer(TransferDTO transferDTO) {
        int amount = transferDTO.getTotalAmount().multiply(BigDecimal.valueOf(100L)).intValue();
        HttpPost httpPost = new HttpPost(wxproperties.getTransferUrl());
        httpPost.addHeader("Accept", "application/json");
        httpPost.addHeader("Content-type", "application/json; charset=utf-8");
        ByteArrayOutputStream bos = new ByteArrayOutputStream();
        ObjectMapper objectMapper = new ObjectMapper();
        ObjectNode rootNode = objectMapper.createObjectNode();
        rootNode.put("appid", wxproperties.getAppId())
                .put("out_batch_no", transferDTO.getOutBatchNo())
                .put("batch_name", transferDTO.getBatchName())
                .put("batch_remark",transferDTO.getBatchRemark())
                .put("total_amount",amount);
        rootNode.putObject("transfer_detail_list")
                .put("out_detail_no",transferDTO.getOutDetailNo())
                .put("transfer_amount",amount)
                .put("transfer_remark",transferDTO.getTransferRemark())
                .put("open_id",transferDTO.getOpenid())
                .put("user_name",transferDTO.getUserName());
        objectMapper.writeValue(bos, rootNode);
        httpPost.setEntity(new StringEntity(bos.toString("UTF-8"), "UTF-8"));
        CloseableHttpResponse response = httpClient.execute(httpPost);
        String bodyAsString = EntityUtils.toString(response.getEntity());
        final JSONObject jsonObject = JSONObject.parseObject(bodyAsString);
        log.info("微信提现返回信息 = " + jsonObject.toJSONString());
        //Assert.isTrue(StrUtil.equals(jsonObject.get()), () -> new BusinessException("支付失败"));
        setOrderStatus(transferDTO.getOutDetailNo(),"提现",null);
        Map<String,Object> transferReturnMap = new HashMap<>(5);
        String timestamp = String.valueOf(System.currentTimeMillis() / 1000);
        transferReturnMap.put("timeStamp", timestamp);
        transferReturnMap.put("status", "success");
        transferReturnMap.put("batch_id", jsonObject.getString("batch_id"));
        transferReturnMap.put("out_batch_no", jsonObject.getString("out_batch_no"));
        transferReturnMap.put("create_time", jsonObject.getString("create_time"));
        return transferReturnMap;
    }

    @SneakyThrows
    public void getTransferOrderDetail(String orderBatchNo,String orderNo) {
        final HttpGet httpGet = new HttpGet(StrUtil.format(wxproperties.getTransferOrderDetailUrl(), orderBatchNo, orderNo));
        httpGet.addHeader("Accept", "application/json");
        httpGet.addHeader("Content-type", "application/json; charset=utf-8");
        final CloseableHttpResponse response = httpClient.execute(httpGet);
        String bodyAsString = EntityUtils.toString(response.getEntity());
        final JSONObject jsonObject = JSONObject.parseObject(bodyAsString);
        final String orderDetailNo = jsonObject.getString("out_detail_no");
        final String detailStatus = jsonObject.getString("detail_status");
        setOrderStatus(orderDetailNo,"提现",detailStatus);

    }




    @SneakyThrows
    public boolean verifiedSign(HttpServletRequest request, String body) {
        //微信返回的证书序列号
        String serialNo = request.getHeader("Wechatpay-Serial");
        //微信返回的随机字符串
        String nonceStr = request.getHeader("Wechatpay-Nonce");
        //微信返回的时间戳
        String timestamp = request.getHeader("Wechatpay-Timestamp");
        //微信返回的签名
        String wechatSign = request.getHeader("Wechatpay-Signature");
        log.info("回调接收到的证书序列号为:{},随机字符串为:{},返回的时间戳为:{},微信返回的签名为:{}", serialNo, nonceStr, timestamp, wechatSign);
        //组装签名字符串
        String signStr = Stream.of(timestamp, nonceStr, body)
                .collect(Collectors.joining("\n", "", "\n"));
        final HttpGet httpGet = new HttpGet(wxproperties.getCertificatesUrl());
        httpGet.addHeader("Accept", "application/json");
        httpGet.addHeader("User-Agent", "https://zh.wikipedia.org/wiki/User_agent");
        final CloseableHttpResponse execute = httpClient.execute(httpGet);
        String bodyAsString = EntityUtils.toString(execute.getEntity());
        log.info("回调证书认证返回信息:{}", JSON.toJSONString(bodyAsString));
        final List<Certificate> certificateList = JSON.parseArray(JSONObject.parseObject(bodyAsString).getString("data"), Certificate.class);
        //根据序列号获取平台证书
        final Certificate.EncryptCertificate encrypt_certificate = certificateList.stream()
                .filter(cert -> StrUtil.equalsIgnoreCase(cert.getSerial_no(), serialNo)).collect(Collectors.toList())
                .get(0).getEncrypt_certificate();
        //获取证书字公钥
        String publicKey = AesUtil.decryptToString(wxproperties.getApiV3Key().getBytes(StandardCharsets.UTF_8),
                encrypt_certificate.getAssociated_data().getBytes(StandardCharsets.UTF_8),
                encrypt_certificate.getNonce().getBytes(StandardCharsets.UTF_8), encrypt_certificate.getCiphertext());
        CertificateFactory cf = CertificateFactory.getInstance("X509");

        //获取证书
        ByteArrayInputStream inputStream = new ByteArrayInputStream(publicKey.getBytes(StandardCharsets.UTF_8));
        X509Certificate certificate = null;
        try {
            certificate = (X509Certificate) cf.generateCertificate(inputStream);
        } catch (CertificateException e) {
            log.error("获取证书失败:{}", e.getMessage());
            e.printStackTrace();
        }
        //获取失败 验证失败
        if (certificate == null) {
            log.error("回调验证失败");
            return false;
        }
        //SHA256withRSA签名
        Signature signature = Signature.getInstance("SHA256withRSA");
        signature.initVerify(certificate);
        signature.update(signStr.getBytes());
        //返回验签结果
        return signature.verify(Base64Utils.decodeFromString(wechatSign));
    }

    public String getRequestBody(HttpServletRequest request) throws IOException {
        InputStream stream = null;
        BufferedReader reader = null;
        StringBuffer sb = new StringBuffer();
        try {
            stream = request.getInputStream();
            // 获取响应
            reader = new BufferedReader(new InputStreamReader(stream));
            String line;
            while ((line = reader.readLine()) != null) {
                sb.append(line);
            }
        } catch (IOException e) {
            throw new IOException("读取返回支付接口数据流出现异常！");
        } finally {
            reader.close();
        }
        return sb.toString();
    }

    @SneakyThrows
    public Map<String, String> payCallBack(HttpServletRequest request) {
        final String requestBody = this.getRequestBody(request);
        log.info("回调接收到的参数为：{}", JSON.toJSON(requestBody));
        Map<String, String> returnMap = new HashMap<>();
        try {
            if (verifiedSign(request, requestBody)) {
                //微信支付通知实体类
                PayCallBackInfo payCallBackInfo = JSONObject.parseObject(requestBody, PayCallBackInfo.class);
                //如果支付成功
                if ("TRANSACTION.SUCCESS".equals(payCallBackInfo.getEvent_type())) {
                    //通知资源数据
                    PayCallBackInfo.Resource resource = payCallBackInfo.getResource();
                    //解密后资源数据
                    String notifyResourceStr = AesUtil.decryptToString(wxproperties.getApiV3Key().getBytes(StandardCharsets.UTF_8),
                            resource.getAssociated_data().getBytes(StandardCharsets.UTF_8),
                            resource.getNonce().getBytes(StandardCharsets.UTF_8), resource.getCiphertext());
                    //通知资源数据对象
                    ResourceInfo decryptResource = JSONObject.parseObject(notifyResourceStr, ResourceInfo.class);
                    final String trade_state = decryptResource.getTrade_state();
                    final String outTradeNo = decryptResource.getOut_trade_no();
                    final Integer total = decryptResource.getAmount().getTotal();
                    final BigDecimal amount = getAmountByOrder(outTradeNo);
                    BigDecimal amountPay = (new BigDecimal(total).divide(new BigDecimal("100"))).setScale(2);
                    Assert.isTrue(amount.equals(amountPay), () -> new BusinessException("订单支付金额与订单记录金额不符合"));
                    setOrderStatus(outTradeNo, "支付", trade_state);
                    returnMap.put("outTradeNo", outTradeNo);
                    returnMap.put("code", trade_state);
                }
            }
        } catch (Exception e) {
            log.error("支付失败原因:{}", e.getMessage());
            returnMap.put("code", "500");
            returnMap.put("message", "系统错误");
            return returnMap;
        }

        return returnMap;
    }
    private BigDecimal getAmountByOrder(String orderNo) {
        ExchangeRecordService exchangeOrderService = (ExchangeRecordService) orderFactory.getBySuffix(OrderInfoConstant.CHARGE_ORDER);
        final ExchangeRecord record = exchangeOrderService.getById(orderNo);
        Assert.isTrue(Objects.nonNull(record), () -> new BusinessException(StrUtil.format("找不到订单号为{}对应的订单记录", orderNo)));
        return record.getAmount();
    }

    @SneakyThrows
    @Transactional(rollbackFor = Exception.class)
    public String refund(String orderNo, BigDecimal totalFee) {
        //BigDecimal orderAmount = BigDecimal.ZERO;

        //Assert.isTrue(orderAmount.equals(totalFee), () -> new BusinessException("退款金额不符合"));
        BigDecimal multiply = totalFee.multiply(new BigDecimal(100));
        final int refundFee = multiply.intValue();
        HttpPost httpPost = new HttpPost(wxproperties.getRefundUrl());
        httpPost.addHeader("Accept", "application/json");
        httpPost.addHeader("Content-type", "application/json; charset=utf-8");
        ByteArrayOutputStream bos = new ByteArrayOutputStream();
        ObjectMapper objectMapper = new ObjectMapper();
        ObjectNode rootNode = objectMapper.createObjectNode();
        rootNode.put("out_trade_no", orderNo)
                .put("out_refund_no", orderNo);
        rootNode.putObject("amount")
                .put("refund", refundFee)
                .put("total", refundFee)
                .put("currency", "CNY");
        objectMapper.writeValue(bos, rootNode);
        httpPost.setEntity(new StringEntity(bos.toString("UTF-8"), "UTF-8"));
        CloseableHttpResponse response = httpClient.execute(httpPost);
        String bodyAsString = EntityUtils.toString(response.getEntity());
        final JSONObject jsonObject = JSONObject.parseObject(bodyAsString);
        final String status = jsonObject.getString("status");
        Assert.isTrue(StrUtil.equalsAnyIgnoreCase(status, "SUCCESS", "PROCESSING"), () -> new BusinessException("退款失败"));
        setOrderStatus(orderNo,"退款",status);

        return "退款成功";
    }

        private void setOrderStatus (String orderNo,String type,String feedBackStatus) {
            final String prefix = StrUtil.subPre(orderNo, 1);
            final boolean success = StrUtil.equals(feedBackStatus, "SUCCESS");
            ExchangeRecordService exchangeOrderService = (ExchangeRecordService) orderFactory.getBySuffix(OrderInfoConstant.CHARGE_ORDER);
            ExchangeRecord record = exchangeOrderService.getById(orderNo);
            BillRecord billRecord = billService.getOne(Wrappers.lambdaQuery(new BillRecord())
                    .eq(BillRecord::getOrderNo, orderNo));
            if (StrUtil.equals(prefix, OrderInfoConstant.CHARGE_ORDER)) {

                if (StrUtil.isNotEmpty(feedBackStatus) && StrUtil.equals(type, "支付")) {
                    final ExchangeRecord exchangeRecord = setStatus(record, feedBackStatus);
                    if (success) {
                        billService.updateById(billRecord.setType(0));
                    }
                    exchangeOrderService.updateById(exchangeRecord);

                }
            } //else if (StrUtil.equals(prefix, OrderInfoConstant.WITHDRAW_ORDER)) {
            // ExchangeRecordService exchangeOrderService = (ExchangeRecordService) orderFactory.getBySuffix(OrderInfoConstant.CHARGE_ORDER);
            // final ExchangeRecord record = exchangeOrderService.getById(orderNo);
            // if (StrUtil.equals(type, "提现") && StrUtil.isNotEmpty(feedBackStatus)) {
            //   record.setExchangeStatus(StrUtil.equals(feedBackStatus, "SUCCESS") ? "已提现" : record.getExchangeStatus());
            //   //   exchangeOrderService.updateById(record);
            // }
            else if (StrUtil.contains(orderNo, OrderInfoConstant.SHOP_CHARGE_NO)) {
                exchangeOrderService.updateById(setStatus(record, feedBackStatus));
            }

        }

    private ExchangeRecord setStatus(ExchangeRecord exchangeRecord, String feedBackStatus) {
        final boolean notPay = StrUtil.equals(feedBackStatus, "NOTPAY");
        final boolean success = StrUtil.equals(feedBackStatus, "SUCCESS");
        if (success) {
            exchangeRecord.setExchangeStatus("已充值");

        } else if (notPay) {
            exchangeRecord.setExchangeStatus("已取消");
        } else {
            exchangeRecord.setExchangeStatus("支付失败");
        }
        return exchangeRecord;
    }

    @SneakyThrows
    private String sign(byte[] message) {
        Signature sign = Signature.getInstance("SHA256withRSA");
        sign.initSign(merchantPrivateKey);
        sign.update(message);
        return Base64.getEncoder().encodeToString(sign.sign());
    }

}
